package com.janetfilter.core.plugin;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 插件类加载器
 * 用于加载插件JAR文件中的类
 * 实现了从JAR文件直接读取类字节码的功能
 */
public class PluginClassLoader extends URLClassLoader {
    // 插件JAR文件
    private final JarFile jarFile;

    /**
     * 创建插件类加载器实例
     *
     * @param jarFile 插件JAR文件
     */
    public PluginClassLoader(JarFile jarFile) {
        // 使用应用类加载器作为父类加载器
        super(new URL[0], PluginClassLoader.class.getClassLoader());
        this.jarFile = jarFile;
    }

    /**
     * 加载类的实现
     * 首先尝试从JAR文件中加载类，如果失败则委托给父类加载器
     *
     * @param name 类名
     * @param resolve 是否解析类
     * @return 加载的类
     * @throws ClassNotFoundException 如果类未找到
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classPath = name.replace('.', '/').concat(".class");
        JarEntry entry = jarFile.getJarEntry(classPath);

        if (null != entry) {
            try {
                // 从JAR文件中读取类字节码
                byte[] classBytes = loadClassBytes(jarFile, entry);
                if (null != classBytes && classBytes.length > 0) {
                    // 定义类
                    return defineClass(name, classBytes, 0, classBytes.length);
                }
            } catch (Throwable ignored) {
            }
        }

        // 如果从JAR文件中加载失败，委托给父类加载器
        return super.findClass(name);
    }

    /**
     * 从JAR文件中读取类字节码
     *
     * @param jarFile JAR文件
     * @param entry JAR条目
     * @return 类字节码
     * @throws IOException 如果读取失败
     */
    private byte[] loadClassBytes(JarFile jarFile, JarEntry entry) throws IOException {
        try (InputStream is = jarFile.getInputStream(entry)) {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int n;

            while ((n = is.read(buffer)) != -1) {
                os.write(buffer, 0, n);
            }

            return os.toByteArray();
        }
    }
}
